藉著重構原先從元件取得資料的方法,我們將對 http 的呼叫封裝在服務裡,隱藏了資料來源的細節。這使我們在元件中在可以更專注於與畫面的互動。並且,如果在兩個元件中都需要取得英雄列表,不必在兩處撰寫相同的程式碼,只需要注入服務使用方法便可取得。日後如資料來源有所異動,也僅需調整 service 中的方法。
當然,當我們從元件中呼叫服務中的方法取得資料後,在資料流到訂閱處之前,可以使用 RxJS 的管道(pipe)加上操作符(operators)來加工資料。因此在不同的元件需求下,可以使用服務的同一個方法,透過不同的加工讓來取得符合需求的資料。
接下來,我們將實作新增英雄功能。在此之前,我們先來思考一個問題:元件(component)還是頁面(page)?
一個簡單的說明是:一個路由對應的就是一個頁面。
打開 app-routing.module.ts
查看目前的路由設定:
const routes: Routes = [
{
path: '',
redirectTo: '/heroes',
pathMatch: 'full'
},
{
path: 'heroes',
children: [
{
path: '',
component: HeroListComponent
},
{
path: ':id',
component: HeroDetailComponent,
}
]
},
]
每個 path 會呈現的畫面就是頁面(page),現在可以看到主要使用了兩個元件 HeroListComponent 、 HeroDetailComponent 來呈現頁面。如果在這個情境下,思考「元件還是頁面」似乎有些匪夷所思。而且,這重要嗎?
我只能說細思極恐。的確,我們現在直接將兩個元件用作頁面,但請記得元件的特點是可複用性。在沒有特別的規範下,元件是可以在同一個 module 裏面被任意使用的;如果你將這個元件匯出(export),那有匯入(import)此 module 的地方都可以使用。
當我們在規劃頁面時,它往往具備特殊性,並非可以任意當作組件放入其他元件中。試想,如果剛加入一個已啟動一陣子的案子,或是接手維護一個專案,當你調整了某個畫面上使用的元件,會預期某個頁面會因為這個調整發生改變嗎——甚至是產生 bug。
在 Angular 裡,我們可以透過元件@Component
裝飾器的元資料中不提供 selector,自然,其他地方就無法使用這個元件。使用 Angular CLI 的話,我們可以在新增元件的時候加入參數 --skip-selector
來自動完成這個改動:
ng g c add-hero --skip-selector // g for generate, c for component
這樣新增元件的 @Component
裝飾器內的元資料就不會有 selector:
import { Component, OnInit } from '@angular/core';
@Component({
templateUrl: './add-hero.component.html',
styleUrls: ['./add-hero.component.css']
})
export class AddHeroComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
如果你安裝了保哥的 Angular Extension Pack,你可以這樣進行操作,一個指令都不用背:
輸入 component 並點擊 enter 後,就會出現是否添加參數的清單:
選擇「Page --skip selector」就會自動新增一個頁面,也就是不提供 selector 的元件。
除了防止頁面層級的元件遭到誤用之外,我們還可以透過思考「元件還是頁面」,來不斷優化自己的專案設計。
以即將開始實作的「新增英雄」功能(AddHeroComponent)為例,我們來思考「元件還是頁面」?
可以想見,「新增英雄」功能的畫面上,我們會有一個輸入英雄資料的表單,當使用者填寫完成後就可以點擊按鈕將資料送出,完成英雄的新增。這個表單,我們應該將它製作成元件,例如 HeroDataFormComponent。你馬上可以想到,我們可能會提供另外一個「編輯英雄」功能,屆時,很大的機率會使用類似、甚至是相同的表單。
如果我們直接將 HeroDataFormComponent 配置路由,也就是將它作為頁面使用,那會有什麼問題?一個可能是,在「新增英雄」功能與「編輯英雄」功能的畫面其實不盡相同,新增英雄的部分也許會額外顯示同一種類型(刺客、坦克、鬥士...)的英雄作為參考,而編輯英雄並不需要顯示這些資訊。
在這種需求下,如果我們直接兩個功能的路由導向表單元件,那我們可能需要依據身處的路由,在表單元件裡動態生產許多不同的東西,例如引用其他元件。當需要判斷的情形越多,這個元件就會越難管理。想用使用這個元件的其他成員,要了解這個元件的邏輯成本也增加了。想想,當你使用一個包好的元件,而元件的 API 非常複雜的時候...。能夠達成目標是好事,但人生是不是可以更簡單一些呢?
在專案結構的設計上,你也可以特別在資料夾或是檔案名稱來區分它們。例如在每個功能模組中(feature module),建立pages
資料夾放置頁面元件。或是在新增不帶 selecor 的元件時,將其命名為 AddHeroPageComponent 諸如此類。
所以,在實作功能時,你會怎麼規劃它的頁面及元件呢?